﻿using Common.FrontEnd;
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Util;
using Util.Props;
namespace FrontEnd.Util
{
    /// <summary>
    /// An AudioFileDataSource generates a stream of audio data from a given audio file. All required information concerning
    /// the audio format are read directly from the file . One would need to call {@link #setAudioFile(java.io.File,String)}
    /// to set the input file, and call {@link #getData} to obtain the Data frames.
    ///
    /// </summary>
    public class AudioFileDataSource : BaseDataProcessor
    {
        private AudioFileReader audioFileReader;
        /** The property for the number of bytes to read from the InputStream each time. */
        [S4Integer(defaultValue = 3200)]
        public static String PROP_BYTES_PER_READ = "bytesPerRead";

        //[S4ComponentList(type = typeof(IConfigurable))]
        //public static String AUDIO_FILE_LISTENERS = "audioFileListners";
        //protected List<AudioFileProcessListener> fileListeners = new ArrayList<AudioFileProcessListener>();


        protected int sampleRate;
        protected int bytesPerRead;
        protected int bytesPerValue;
        private long totalValuesRead;
        protected Boolean bigEndian;
        protected Boolean signedData=true;
        private Boolean streamEndReached;
        private Boolean utteranceEndSent;
        private Boolean utteranceStarted;


        public AudioFileDataSource()
        {
        }

        override public void newProperties(PropertySheet ps)
        {
            base.newProperties(ps);
            create(ps.getInt(PROP_BYTES_PER_READ)/*, ps.getComponentList(AUDIO_FILE_LISTENERS, AudioFileProcessListener.class)*/);
        }

        private void create( int bytesPerRead/*, List<AudioFileProcessListener> listeners*/) 
        {
            this.bytesPerRead = bytesPerRead;

            //if( listeners != null ) {
            //    // attach all pool-listeners
            //    for (AudioFileProcessListener configurable : listeners) {
            //        addNewFileListener(configurable);
            //    }
            //}

            initialize();
        }

        override public void initialize()
        {
            base.initialize();
            // reset all stream tags
            streamEndReached = false;
            utteranceEndSent = false;
            utteranceStarted = false;

            if (bytesPerRead % 2 == 1)
            {
                bytesPerRead++;
            }
        }

        public void setAudioFile(String streamName) 
        {
            try 
            {
                audioFileReader = new AudioFileReader(streamName);
            }  
            catch (Exception e) 
            {
                Trace.WriteLine(e.ToString());
            }

            //curAudioFile = new File(audioFileURL.getFile());
            //for (AudioFileProcessListener fileListener : fileListeners)
            //    fileListener.audioFileProcStarted(curAudioFile);

            setInputStream(audioFileReader);
        }

        public void setInputStream(AudioFileReader inputStream)
        {
            streamEndReached = false;
            utteranceEndSent = false;
            utteranceStarted = false;

            WaveFormat format = inputStream.WaveFormat;
            sampleRate = (int)format.SampleRate;
            bigEndian = true;

            String s = format.ToString();
            Trace.WriteLine("input format is " + s);

            //.getSampleSizeInBits()
            if (format.BitsPerSample % 8 != 0)
                throw new Exception("StreamDataSource: bits per sample must be a multiple of 8.");
            bytesPerValue = format.BitsPerSample / 8;

            // test whether all files in the stream have the same format

            WaveFormatEncoding encoding = format.Encoding;
            if (encoding == WaveFormatEncoding.Pcm)
                signedData = true;

            totalValuesRead = 0;
        }

        
        override public IData getData()
        {
            getTimer().start();
            IData output = null;
            if (streamEndReached)
            {
                if (!utteranceEndSent)
                {
                    // since 'firstSampleNumber' starts at 0, the last
                    // sample number should be 'totalValuesRead - 1'
                    output = createDataEndSignal();
                    utteranceEndSent = true;
                }
            }
            else
            {
                if (!utteranceStarted)
                {
                    utteranceStarted = true;
                    output = new DataStartSignal(sampleRate);
                }
                else
                {
                    if (audioFileReader != null)
                    {
                        output = readNextFrame();
                        //for (int i = 0; i<((DoubleData)output).dimension();i++ )
                        //    Trace.WriteLine(((DoubleData)output).Values[i]);
                        if (output == null)
                        {
                            if (!utteranceEndSent)
                            {
                                output = createDataEndSignal();
                                utteranceEndSent = true;
                            }
                        }
                    }
                }
            }
            getTimer().stop();
            return output;
        }

        private DataEndSignal createDataEndSignal() 
        {
            return new DataEndSignal(audioFileReader.TotalTime.Ticks);
        }

        //private IData readNextFrame()
        //{
        //    long firstSample = totalValuesRead;
        //    int totalRead = 0;
        //    int bytesToRead = bytesPerRead/4;

        //    float[] doubleData= new float[bytesToRead];
        //    int cate = audioFileReader.Read(doubleData, totalRead, bytesToRead);
        //    totalValuesRead += cate;
        //    return new DoubleData(doubleData, sampleRate, firstSample);
        //}

        private IData readNextFrame()
        {
            // read one frame's worth of bytes
            int read;
            int totalRead = 0;
            int bytesToRead = bytesPerRead;
            byte[] samplesBuffer = new byte[bytesPerRead];
            long firstSample = totalValuesRead;
            try
            {
                do
                {
                    read = audioFileReader.Read(samplesBuffer, totalRead, bytesToRead
                            - totalRead);
                    if (read > 0)
                    {
                        totalRead += read;
                    }
                } while (read != -1 && totalRead < bytesToRead);
                if (totalRead <= 0)
                {
                    closeDataStream();
                    return null;
                }
                // shrink incomplete frames
                totalValuesRead += (totalRead / bytesPerValue);
                if (totalRead < bytesToRead)
                {
                    totalRead = (totalRead % 2 == 0)
                            ? totalRead + 2
                            : totalRead + 3;
                    byte[] shrinkedBuffer = new byte[totalRead];
                    //System
                    //        .arraycopy(samplesBuffer, 0, shrinkedBuffer, 0,
                    //                totalRead);
                    samplesBuffer = shrinkedBuffer;
                    closeDataStream();
                }
            }
            catch (Exception ioe)
            {
                throw new Exception("Error reading data", ioe);
            }
            // turn it into an Data object
            double[] doubleData;
            if (bigEndian)
            {
                doubleData = DataUtil.bytesToValues(samplesBuffer, 0, totalRead, bytesPerValue, signedData);
            }
            else
            {
                doubleData = DataUtil.littleEndianBytesToValues(samplesBuffer, 0, totalRead, bytesPerValue, signedData);
            }

            return new DoubleData(doubleData, sampleRate, firstSample);
        }

         private void closeDataStream()
         {
            streamEndReached = true;
            if (audioFileReader != null)
            {
                audioFileReader.Close();
            }
        }
    }
}
